<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>语音识别服务 - Speaker ASR</title>
    <script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            border-radius: 16px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
            overflow: hidden;
        }
        
        .header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 30px;
            text-align: center;
        }
        
        .header h1 {
            font-size: 32px;
            margin-bottom: 10px;
        }
        
        .header p {
            opacity: 0.9;
            font-size: 16px;
        }
        
        .content {
            padding: 30px;
        }
        
        .section {
            margin-bottom: 30px;
            padding: 25px;
            background: #f8f9fa;
            border-radius: 12px;
            border: 1px solid #e9ecef;
        }
        
        .section h2 {
            color: #667eea;
            margin-bottom: 20px;
            font-size: 20px;
            display: flex;
            align-items: center;
        }
        
        .section h2::before {
            content: '';
            width: 4px;
            height: 20px;
            background: #667eea;
            margin-right: 10px;
            border-radius: 2px;
        }
        
        .form-group {
            margin-bottom: 20px;
        }
        
        .form-group label {
            display: block;
            margin-bottom: 8px;
            color: #495057;
            font-weight: 500;
        }
        
        .form-group input[type="file"],
        .form-group input[type="number"],
        .form-group select {
            width: 100%;
            padding: 12px;
            border: 2px solid #e9ecef;
            border-radius: 8px;
            font-size: 14px;
            transition: all 0.3s;
        }
        
        .form-group input:focus,
        .form-group select:focus {
            outline: none;
            border-color: #667eea;
            box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
        }
        
        .form-row {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
        }
        
        .btn {
            padding: 12px 30px;
            border: none;
            border-radius: 8px;
            font-size: 16px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
        }
        
        .btn-primary {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
        }
        
        .btn-primary:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
        }
        
        .btn-secondary {
            background: #6c757d;
            color: white;
        }
        
        .btn-secondary:hover {
            background: #5a6268;
        }
        
        .btn:disabled {
            opacity: 0.6;
            cursor: not-allowed;
        }
        
        .file-list {
            margin-top: 15px;
            padding: 15px;
            background: white;
            border-radius: 8px;
            border: 1px solid #e9ecef;
        }
        
        .file-item {
            padding: 10px;
            margin-bottom: 8px;
            background: #f8f9fa;
            border-radius: 6px;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        
        .file-item:last-child {
            margin-bottom: 0;
        }
        
        .progress-container {
            margin-top: 15px;
        }
        
        .progress-item {
            margin-bottom: 20px;
            padding: 20px;
            background: white;
            border-radius: 8px;
            border: 1px solid #e9ecef;
        }
        
        .progress-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
        }
        
        .progress-file {
            font-weight: 600;
            color: #495057;
        }
        
        .progress-stage {
            display: inline-block;
            padding: 4px 12px;
            border-radius: 12px;
            font-size: 12px;
            font-weight: 600;
        }
        
        .stage-loading { background: #ffeaa7; color: #d63031; }
        .stage-diarization { background: #74b9ff; color: #0984e3; }
        .stage-recognition { background: #a29bfe; color: #6c5ce7; }
        .stage-completed { background: #55efc4; color: #00b894; }
        .stage-error { background: #ff7675; color: #d63031; }
        
        .progress-bar-container {
            width: 100%;
            height: 8px;
            background: #e9ecef;
            border-radius: 4px;
            overflow: hidden;
            margin: 10px 0;
        }
        
        .progress-bar {
            height: 100%;
            background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
            transition: width 0.3s;
            border-radius: 4px;
        }
        
        .progress-message {
            font-size: 14px;
            color: #6c757d;
            margin-top: 8px;
        }
        
        .results {
            margin-top: 20px;
        }
        
        .result-item {
            padding: 15px;
            background: white;
            border-radius: 8px;
            border: 1px solid #e9ecef;
            margin-bottom: 10px;
        }
        
        .result-item h4 {
            color: #667eea;
            margin-bottom: 10px;
        }
        
        .result-stats {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
            gap: 10px;
            margin-top: 10px;
        }
        
        .stat {
            padding: 10px;
            background: #f8f9fa;
            border-radius: 6px;
            text-align: center;
        }
        
        .stat-label {
            font-size: 12px;
            color: #6c757d;
            margin-bottom: 5px;
        }
        
        .stat-value {
            font-size: 18px;
            font-weight: 600;
            color: #495057;
        }
        
        .alert {
            padding: 15px 20px;
            border-radius: 8px;
            margin-bottom: 20px;
        }
        
        .alert-success {
            background: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }
        
        .alert-error {
            background: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }
        
        .alert-info {
            background: #d1ecf1;
            color: #0c5460;
            border: 1px solid #bee5eb;
        }
        
        .hidden {
            display: none;
        }
        
        .loading {
            display: inline-block;
            width: 20px;
            height: 20px;
            border: 3px solid rgba(255,255,255,.3);
            border-radius: 50%;
            border-top-color: white;
            animation: spin 1s ease-in-out infinite;
        }
        
        @keyframes spin {
            to { transform: rotate(360deg); }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>🎙️ 语音识别服务</h1>
            <p>基于 Spring Boot 3 的说话人分割与语音识别系统</p>
        </div>
        
        <div class="content">
            <!-- 文件上传区域 -->
            <div class="section">
                <h2>📁 上传音频文件</h2>
                <div class="form-group">
                    <label for="audioFiles">选择音频文件（支持 WAV, MP3, FLAC, OGG, M4A 等格式）</label>
                    <input type="file" id="audioFiles" multiple accept="audio/*">
                </div>
                <button class="btn btn-primary" onclick="uploadFiles()">
                    <span>⬆️ 上传文件</span>
                </button>
                <div id="fileList" class="file-list hidden"></div>
            </div>
            
            <!-- 配置选项 -->
            <div class="section">
                <h2>⚙️ 配置选项</h2>
                
                <div class="form-row">
                    <div class="form-group">
                        <label for="modelSelect">ASR 模型</label>
                        <select id="modelSelect">
                            <option value="">加载中...</option>
                        </select>
                    </div>
                    
                    <div class="form-group">
                        <label for="languageSelect">语言</label>
                        <select id="languageSelect">
                            <option value="auto">自动检测</option>
                            <option value="zh">中文（国语）</option>
                            <option value="yue">粤语</option>
                        </select>
                    </div>
                </div>
                
                <div class="form-row">
                    <div class="form-group">
                        <label for="numSpeakers">说话人数量（-1 表示自动检测）</label>
                        <input type="number" id="numSpeakers" value="-1" min="-1" max="20">
                    </div>
                    
                    <div class="form-group">
                        <label for="clusterThreshold">聚类阈值（0-1）</label>
                        <input type="number" id="clusterThreshold" value="0.7" min="0" max="1" step="0.1">
                    </div>
                </div>
                
                <div class="form-row">
                    <div class="form-group">
                        <label for="numProcesses">并行进程数</label>
                        <input type="number" id="numProcesses" value="1" min="1" max="16">
                    </div>
                    
                    <div class="form-group">
                        <label for="numGpus">GPU 数量</label>
                        <input type="number" id="numGpus" value="1" min="1" max="8">
                    </div>
                </div>
                
                <button class="btn btn-primary" id="startBtn" onclick="startTask()" disabled>
                    <span>🚀 开始识别</span>
                </button>
            </div>
            
            <!-- 进度显示 -->
            <div class="section" id="progressSection" style="display: none;">
                <h2>📊 处理进度</h2>
                <div id="progressContainer" class="progress-container"></div>
            </div>
            
            <!-- 结果显示 -->
            <div class="section" id="resultsSection" style="display: none;">
                <h2>✅ 处理结果</h2>
                <div id="resultsContainer" class="results"></div>
            </div>
        </div>
    </div>
    
    <script>
        const API_BASE = 'http://localhost:8080/api';
        let uploadedFiles = [];
        let stompClient = null;
        let currentTaskId = null;
        let fileProgressMap = {};
        
        // 页面加载时初始化
        window.onload = function() {
            loadModels();
        };
        
        // 加载模型列表
        async function loadModels() {
            try {
                const response = await fetch(`${API_BASE}/models/asr`);
                const result = await response.json();
                const select = document.getElementById('modelSelect');
                select.innerHTML = '';
                
                result.data.forEach(model => {
                    const option = document.createElement('option');
                    option.value = model.id;
                    option.text = `${model.name} - ${model.description}`;
                    select.appendChild(option);
                });
            } catch (error) {
                console.error('加载模型失败:', error);
                showAlert('加载模型失败: ' + error.message, 'error');
            }
        }
        
        // 上传文件
        async function uploadFiles() {
            const fileInput = document.getElementById('audioFiles');
            const files = fileInput.files;
            
            if (files.length === 0) {
                showAlert('请选择音频文件', 'error');
                return;
            }
            
            const formData = new FormData();
            for (let file of files) {
                formData.append('files', file);
            }
            
            try {
                showAlert('正在上传文件...', 'info');
                const response = await fetch(`${API_BASE}/files/upload/batch`, {
                    method: 'POST',
                    body: formData
                });
                const result = await response.json();
                
                if (result.code === 200) {
                    uploadedFiles = result.data;
                    displayFileList();
                    document.getElementById('startBtn').disabled = false;
                    showAlert(`成功上传 ${uploadedFiles.length} 个文件`, 'success');
                } else {
                    showAlert('上传失败: ' + result.message, 'error');
                }
            } catch (error) {
                console.error('上传失败:', error);
                showAlert('上传失败: ' + error.message, 'error');
            }
        }
        
        // 显示文件列表
        function displayFileList() {
            const fileList = document.getElementById('fileList');
            fileList.classList.remove('hidden');
            fileList.innerHTML = '<h4 style="margin-bottom: 10px;">已上传的文件:</h4>';
            
            uploadedFiles.forEach((filePath, index) => {
                const fileName = filePath.split(/[/\\]/).pop();
                const fileItem = document.createElement('div');
                fileItem.className = 'file-item';
                fileItem.innerHTML = `
                    <span>${index + 1}. ${fileName}</span>
                    <span style="color: #28a745;">✓</span>
                `;
                fileList.appendChild(fileItem);
            });
        }
        
        // 开始任务
        async function startTask() {
            if (uploadedFiles.length === 0) {
                showAlert('请先上传音频文件', 'error');
                return;
            }
            
            try {
                document.getElementById('startBtn').disabled = true;
                showAlert('正在创建任务...', 'info');
                
                // 获取选中的模型配置
                const modelId = document.getElementById('modelSelect').value;
                const modelResponse = await fetch(`${API_BASE}/models/asr/${modelId}`);
                const modelResult = await modelResponse.json();
                const model = modelResult.data;
                
                // 获取说话人模型配置
                const speakerResponse = await fetch(`${API_BASE}/models/speaker`);
                const speakerResult = await speakerResponse.json();
                const speaker = speakerResult.data;
                
                // 构建请求
                const config = {
                    speakerSegmentationModel: speaker.segmentation,
                    speakerEmbeddingModel: speaker.embedding,
                    asrModelPath: model.modelPath,
                    asrTokensPath: model.tokensPath,
                    asrModelType: model.type,
                    numSpeakers: parseInt(document.getElementById('numSpeakers').value),
                    clusterThreshold: parseFloat(document.getElementById('clusterThreshold').value),
                    language: document.getElementById('languageSelect').value,
                    numProcesses: parseInt(document.getElementById('numProcesses').value),
                    numGpus: parseInt(document.getElementById('numGpus').value),
                    provider: 'cuda'
                };
                
                // 只有 Whisper 模型才添加 encoder 和 decoder
                if (model.encoderPath) {
                    config.whisperEncoder = model.encoderPath;
                }
                if (model.decoderPath) {
                    config.whisperDecoder = model.decoderPath;
                }
                
                const request = {
                    audioFiles: uploadedFiles,
                    config: config
                };
                
                // 调试：打印请求内容
                console.log('发送请求:', JSON.stringify(request, null, 2));
                
                // 创建任务
                const response = await fetch(`${API_BASE}/asr/tasks`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(request)
                });
                
                // 调试：打印响应
                console.log('响应状态:', response.status);
                const result = await response.json();
                console.log('响应内容:', result);
                
                if (result.code === 200) {
                    currentTaskId = result.data.taskId;
                    showAlert('任务创建成功，开始处理...', 'success');
                    
                    // 显示进度区域
                    document.getElementById('progressSection').style.display = 'block';
                    document.getElementById('resultsSection').style.display = 'none';
                    
                    // 初始化进度显示
                    initProgressDisplay(uploadedFiles);
                    
                    // 连接 WebSocket 监听进度
                    connectWebSocket(currentTaskId);
                } else {
                    showAlert('创建任务失败: ' + result.message, 'error');
                    document.getElementById('startBtn').disabled = false;
                }
            } catch (error) {
                console.error('创建任务失败:', error);
                showAlert('创建任务失败: ' + error.message, 'error');
                document.getElementById('startBtn').disabled = false;
            }
        }
        
        // 初始化进度显示
        function initProgressDisplay(files) {
            const container = document.getElementById('progressContainer');
            container.innerHTML = '';
            fileProgressMap = {};
            
            files.forEach((filePath, index) => {
                const fileName = filePath.split(/[/\\]/).pop();
                const progressItem = document.createElement('div');
                progressItem.className = 'progress-item';
                progressItem.id = `progress-${index}`;
                progressItem.innerHTML = `
                    <div class="progress-header">
                        <span class="progress-file">${fileName}</span>
                        <span class="progress-stage stage-loading">等待处理</span>
                    </div>
                    <div class="progress-bar-container">
                        <div class="progress-bar" style="width: 0%"></div>
                    </div>
                    <div class="progress-message">等待开始...</div>
                `;
                container.appendChild(progressItem);
                fileProgressMap[fileName] = { index, progress: 0 };
            });
        }
        
        // 连接 WebSocket
        function connectWebSocket(taskId) {
            const socket = new SockJS(`${API_BASE}/ws`);
            stompClient = Stomp.over(socket);
            
            stompClient.connect({}, function(frame) {
                console.log('WebSocket 连接成功');
                
                // 订阅进度
                stompClient.subscribe(`/topic/progress/${taskId}`, function(message) {
                    const progress = JSON.parse(message.body);
                    updateProgress(progress);
                });
                
                // 订阅完成通知
                stompClient.subscribe(`/topic/task/${taskId}`, function(message) {
                    const task = JSON.parse(message.body);
                    handleTaskComplete(task);
                });
            }, function(error) {
                console.error('WebSocket 连接失败:', error);
                showAlert('实时进度连接失败', 'error');
            });
        }
        
        // 更新进度显示
        function updateProgress(progress) {
            const fileInfo = fileProgressMap[progress.fileName];
            if (!fileInfo) return;
            
            const progressItem = document.getElementById(`progress-${fileInfo.index}`);
            if (!progressItem) return;
            
            // 更新进度条
            const progressBar = progressItem.querySelector('.progress-bar');
            progressBar.style.width = `${progress.progress}%`;
            
            // 更新阶段标签
            const stageLabel = progressItem.querySelector('.progress-stage');
            stageLabel.className = `progress-stage stage-${progress.stage}`;
            stageLabel.textContent = getStageText(progress.stage);
            
            // 更新消息
            const messageDiv = progressItem.querySelector('.progress-message');
            messageDiv.textContent = `${progress.message} (GPU ${progress.gpuId})`;
            
            fileInfo.progress = progress.progress;
        }
        
        // 获取阶段文本
        function getStageText(stage) {
            const stageMap = {
                'loading': '加载中',
                'diarization': '说话人分割',
                'recognition': '语音识别',
                'completed': '已完成',
                'error': '错误'
            };
            return stageMap[stage] || stage;
        }
        
        // 处理任务完成
        function handleTaskComplete(task) {
            console.log('任务完成:', task);
            
            if (task.status === 'COMPLETED') {
                showAlert('所有文件处理完成！', 'success');
                displayResults(task);
            } else if (task.status === 'FAILED') {
                showAlert('任务失败: ' + task.errorMessage, 'error');
            }
            
            document.getElementById('startBtn').disabled = false;
            
            // 断开 WebSocket
            if (stompClient) {
                stompClient.disconnect();
            }
        }
        
        // 显示结果
        function displayResults(task) {
            const resultsSection = document.getElementById('resultsSection');
            const resultsContainer = document.getElementById('resultsContainer');
            
            resultsSection.style.display = 'block';
            resultsContainer.innerHTML = '';
            
            // 总体统计
            const statsDiv = document.createElement('div');
            statsDiv.className = 'result-item';
            statsDiv.innerHTML = `
                <h4>📈 总体统计</h4>
                <div class="result-stats">
                    <div class="stat">
                        <div class="stat-label">处理文件数</div>
                        <div class="stat-value">${task.audioFiles.length}</div>
                    </div>
                    <div class="stat">
                        <div class="stat-label">总时长</div>
                        <div class="stat-value">${task.totalDuration ? task.totalDuration.toFixed(2) + 's' : 'N/A'}</div>
                    </div>
                    <div class="stat">
                        <div class="stat-label">处理时间</div>
                        <div class="stat-value">${task.processingTime ? task.processingTime.toFixed(2) + 's' : 'N/A'}</div>
                    </div>
                    <div class="stat">
                        <div class="stat-label">实时率 (RTF)</div>
                        <div class="stat-value">${task.overallRtf ? task.overallRtf.toFixed(3) : 'N/A'}</div>
                    </div>
                </div>
            `;
            resultsContainer.appendChild(statsDiv);
            
            // 输出文件列表
            if (task.resultFiles && task.resultFiles.length > 0) {
                const filesDiv = document.createElement('div');
                filesDiv.className = 'result-item';
                filesDiv.innerHTML = '<h4>📄 输出文件</h4>';
                
                task.resultFiles.forEach(file => {
                    const fileName = file.split(/[/\\]/).pop();
                    const fileItem = document.createElement('div');
                    fileItem.className = 'file-item';
                    fileItem.innerHTML = `
                        <span>${fileName}</span>
                        <span style="color: #28a745;">✓</span>
                    `;
                    filesDiv.appendChild(fileItem);
                });
                
                resultsContainer.appendChild(filesDiv);
            }
        }
        
        // 显示提示信息
        function showAlert(message, type) {
            // 移除旧的提示
            const oldAlert = document.querySelector('.alert');
            if (oldAlert) {
                oldAlert.remove();
            }
            
            const alert = document.createElement('div');
            alert.className = `alert alert-${type}`;
            alert.textContent = message;
            
            const content = document.querySelector('.content');
            content.insertBefore(alert, content.firstChild);
            
            // 3秒后自动移除（除了 info 类型）
            if (type !== 'info') {
                setTimeout(() => {
                    alert.remove();
                }, 3000);
            }
        }
    </script>
</body>
</html>
